The Java programming language is recognized to provide many benefits to the programmer. Not the least of these benefits relate to the handling of error conditions, support for multiple threads (to be defined hereinafter) and platform independence.
A defined unit of programming code, developed for a particular purpose, has been called a function, a subroutine and a procedure in different programming languages. In the Java programming language, such a unit is called a “method”.
Java includes provisions for handling unusual error conditions. The provisions are included in the form of “exceptions”, which allow unusual error conditions to be handled without including “if” statements to deal with every possible error condition.
Java also includes provisions for multiple executions streams running in parallel. Such executions streams are called “threads”.
One of the desirable qualities of Java, is the ability of Java code to be executed on a wide range of computing platforms, where each of the computing platforms in the range can run a Java Virtual Machine. However, there remains a requirement that the code run on a particular platform be native to that platform. A just-in-time (JIT) Java compiler is typically in place, as part of an environment in which Java code is to be executed, to convert Java byte code into native code for the platform on which the code is to be executed.
Recent JIT compilers have improved features, such as “method inlining” and “synchronization”.
Where a method is called that does something trivial, like add one to an argument and then return the argument, Java programmers have, in the past, been tempted to simply insert the instruction rather than call the method. This act is called manual method inlining. Such manual method inlining can improve the speed at which the code runs as the overhead of an instruction that jumps to the called method, as well as the return from the method, is saved. Some JIT compilers have the capability to recognize where method inlining will improve the speed at which code runs and, thus, can automatically inline methods while converting the byte code into native code.
From Venners, Bill, “How the Java virtual machine performs thread synchronization”, http://www.javaworld.com/javaworld/jw-07-1997/jw-07-hood.html, in the Java virtual machine (JVM), each thread is awarded a Java stack, which contains data no other thread can access. If multiple threads need to use the same objects or class variables concurrently, the access of the threads to the data must be properly managed. Otherwise, the program will have unpredictable behavior.
To coordinate shared data access among multiple threads, the Java virtual machine associates a lock with each object. A thread needing to lock a particular object, communicates this requirement to the JVM. The JVM may then provide the lock to the thread. When the thread no longer requires the lock, the thread communicates this lack of requirement to the JVM. If a second thread has requested the same lock, the JVM provides the lock to the second thread.
A single thread is allowed to lock the same object multiple times. For each object, the JVM maintains a count of the number of times the object has been locked. An unlocked object has a count of zero. When a thread acquires the lock for the first time, the count is incremented to one. Each time the thread acquires a lock on the same object, the count is incremented. Each time the thread releases the lock, the count is decremented. When the count reaches zero, the lock is released and made available to other threads.
In Java language terminology, the coordination of multiple threads that must access shared data is called synchronization. The Java language provides two built-in ways to synchronize access to data: with synchronized statements or synchronized methods.
Two bytecodes, namely “monitorenter” and “monitorexit”, are used for synchronization blocks within methods. When the monitorenter bytecode is encountered by the Java virtual machine, the Java virtual machine acquires the lock for the object referred to by object ref on the stack. If the thread already owns the lock for that object, a count is incremented. Each time the monitorexit bytecode is executed for the thread on the object, the count is decremented. When the count reaches zero, the lock is released.
When an exception occurs in an executing Java method, any locks on the objects with which the method has been interacting must be released. As will be apparent to a person skilled in the art, the locks that must be released do not include the locks that are held at entry to the handler's try region. In a known JIT compiler, monitorenter and monitorexit locking operations use a “try region” and a handler to guarantee that an object is unlocked even if an exception occurs. Such a handler effectively catches and re-throws exceptions to guarantee that each locked object is unlocked. Such an approach may be considered costly in terms of execution time required to throw and handle multiple exceptions.